package nsoft.timeline.util;

import java.awt.Color;
import java.awt.Font;
import java.awt.Graphics2D;
import java.awt.Image;
import java.awt.Rectangle;
import java.awt.RenderingHints;
import java.awt.font.FontRenderContext;
import java.awt.font.LineBreakMeasurer;
import java.awt.font.TextAttribute;
import java.awt.font.TextLayout;
import java.awt.image.BufferedImage;
import java.io.File;
import java.io.IOException;
import java.io.PrintWriter;
import java.net.URL;
import java.text.AttributedCharacterIterator;
import java.text.AttributedString;
import java.util.List;
import java.util.UUID;

import javax.imageio.ImageIO;
import javax.servlet.http.HttpServletRequest;
import javax.servlet.http.HttpServletResponse;

import org.apache.commons.lang3.StringEscapeUtils;
import org.apache.tomcat.util.http.fileupload.FileItem;
import org.apache.tomcat.util.http.fileupload.FileUploadException;
import org.apache.tomcat.util.http.fileupload.disk.DiskFileItemFactory;
import org.apache.tomcat.util.http.fileupload.servlet.ServletFileUpload;
import org.htmlparser.Node;
import org.htmlparser.Parser;
import org.htmlparser.filters.HasAttributeFilter;
import org.htmlparser.filters.TagNameFilter;
import org.htmlparser.nodes.TagNode;
import org.htmlparser.tags.ImageTag;
import org.htmlparser.tags.MetaTag;
import org.htmlparser.tags.TitleTag;
import org.htmlparser.util.NodeList;
import org.htmlparser.util.ParserException;
import org.jsoup.Jsoup;

public class util {
	static int MAX_FILE_SIZE = 5 * 1024 * 1024;
	static int MAX_MEM_SIZE = 5 * 1024 * 1024;
	static String SPLITTER = "<nsoft_splitter>";
	static String INLINE_SPLITTER = "<nsoft_inline_splitter>";
	static String DATA_SPLITTER="<nsoft_data_splitter>";
	static String PRIME = "<nsoft_prime>";
	static String DOUBLE_PRIME = "<nsoft_dprime>";
	static String TEMP_PATH = "temp";
	static String TEMP_THUMB_PATH = "thumb";
	static String EXT_ARRAY = "jpg png gif bmp";
	static String ARTICLE_LINK = "article_link";
	static int STD_IMAGE_WIDTH = 920;
	static int STD_IMAGE_HEIGHT = 460;
	
	/**
	 * to upload an image to server 
	 * 
	 * @param req
	 * @param resp
	 */
	public static void upload(HttpServletRequest req, HttpServletResponse resp){
		String contentType = req.getContentType();
		String tempPath = System.getProperty("wtp.deploy") + 
											"/timeline/" + TEMP_PATH;
		String thumbPath = tempPath + "/" + TEMP_THUMB_PATH;
		
		boolean isPathExist = new File(tempPath).exists();
		if (!isPathExist){
			new File(tempPath).mkdir();
			new File(thumbPath).mkdir();
		}
		String output = "";
		PrintWriter out = null;
		try{
			out = resp.getWriter();
		}catch(IOException ioEx){
			System.err.println(ioEx.getClass() + ": " + ioEx.getMessage());
		}
		if (out == null) {
			System.err.println("server is not responsible.");
			return;
		}
		
        if ((contentType != null) && 
        		(contentType.indexOf("multipart/form-data") >= 0)){
        	DiskFileItemFactory factory = new DiskFileItemFactory();
        	factory.setSizeThreshold(MAX_MEM_SIZE);
        	
        	ServletFileUpload upload = new ServletFileUpload(factory);
        	upload.setFileSizeMax(MAX_FILE_SIZE);
        	
        	@SuppressWarnings("rawtypes")
        	List fileItems = null;
        	try {
        		fileItems = upload.parseRequest(req);
        	}catch(FileUploadException fuEx){
        		String error = fuEx.getClass() + ": " + fuEx.getMessage();
        		System.err.println(error);
        		output = "0" + SPLITTER + error + SPLITTER + SPLITTER + "0";
        	}
        	
        	if (fileItems != null){
	        	FileItem fi = (FileItem)fileItems.iterator().next();
	        	if (!fi.isFormField()){
	        		String fileName = fi.getName();
	        		String ext = fileName.substring(fileName.lastIndexOf("."));
	        		String newFilename = UUID.randomUUID().toString() + ext;
	        		long sizeInBytes=fi.getSize();
	        		
	        		String filepath = tempPath + "/" + newFilename;
	        		File file = new File(filepath);
	
	        		try {
	        			fi.write(file);
	        			String[] exdata=createThumb(file,thumbPath).split(SPLITTER);
	        			String newThumbFile=exdata[0]; 
	        			String newThumbFileName = new File(newThumbFile).getName();
	        			output = "1" + SPLITTER + "./" + 
	        				TEMP_PATH + "/" + TEMP_THUMB_PATH + "/" + 
	        					newThumbFileName +SPLITTER + 
	        				"./" + TEMP_PATH + "/" + newFilename + SPLITTER + 
		        				exdata[1]+DATA_SPLITTER+
		        				exdata[2]+DATA_SPLITTER+
		        				sizeInBytes+
	        				SPLITTER+
	        				SPLITTER+
	        				SPLITTER;
	        		}catch(Exception ex){
	        			String error = ex.getClass() + ": " + ex.getMessage();
	        			System.err.println(error);
	        			output = "100" + SPLITTER + error + SPLITTER + SPLITTER + "0";
	        		}
	        	} 
            }
        	out.println(output);
        	out.flush();
        	
        	out.close();
        }else{
        	System.err.println("content-type is null due to a problem at server");
        }
	}
	
	/**
	 * to analyze a webpage to get basic contents (image, title, description)
	 * 
	 * @param req
	 * @param resp
	 */
	public static void analyze(HttpServletRequest req, HttpServletResponse resp){
		String tempPath = System.getProperty("wtp.deploy") + 
							"/timeline/" + TEMP_PATH;
		String thumbPath = tempPath + "/" + TEMP_THUMB_PATH;

		boolean isPathExist = new File(tempPath).exists();
		if (!isPathExist){
			new File(tempPath).mkdir();
			new File(thumbPath).mkdir();
		}

		String output="";
		PrintWriter out = null;
		try{
			resp.setContentType("text/html; charset=UTF-8");
			out = resp.getWriter();
		}catch(IOException ioEx){
			System.err.println(ioEx.getClass() + ": " + ioEx.getMessage());
		}
		if (out == null) {
			System.err.println("server is not responsible.");
			return;
		}

		String url=req.getParameter(ARTICLE_LINK);
		if (url==null || url.equals("")){
			output= "0"+SPLITTER+"url is empty"+
					SPLITTER+SPLITTER+
					SPLITTER+SPLITTER;
		}else{
		
			if (!url.contains("http://") && !url.contains("https://"))
				url="http://"+url;
			
        	String newFilename = UUID.randomUUID().toString() + ".png";
        	
        	String[] info= null;
        	boolean getPageSuccess=false;
        	try {
        		info=getPageInfo(url);
        		for (int i=0; i<info.length;i++){
        			info[i]=fixSpecialChars(info[i]);
        		}
        		getPageSuccess=true;
        	}catch(ParserException pEx){
    			String errMsg="error at analyzing module. "+
    					pEx.getClass()+ ": " +pEx.getMessage();
    			System.err.println(errMsg);
    			output="100"+SPLITTER+errMsg+
    					SPLITTER+SPLITTER+
    					SPLITTER+SPLITTER;
        	}
    		
        	if (getPageSuccess){
	    		try{
	    			String imgNewFile=createMainImage(
	    								info,newFilename,tempPath);
	    			String imgNewFileName=new File(imgNewFile).getName();
        			
	    			String[] exdata=createThumb(new File(imgNewFile), 
	    					thumbPath).split(SPLITTER);
	    			String thumbImg=exdata[0];
	    			String thumbImgFileName=new File(thumbImg).getName();
	    			
	    			output= "1"+SPLITTER+
	    					"./"+TEMP_PATH+"/"+TEMP_THUMB_PATH+"/"+
	    								thumbImgFileName+ SPLITTER+
	    					"./"+TEMP_PATH+"/"+imgNewFileName+SPLITTER+
	    						exdata[1]+DATA_SPLITTER+
		        				exdata[2]+DATA_SPLITTER+
		        				"0"+DATA_SPLITTER+
			    					info[0]+INLINE_SPLITTER+
			        				info[1]+INLINE_SPLITTER+
			        				info[2]+INLINE_SPLITTER+
			        				url+
			        		SPLITTER+
	        				"0"+SPLITTER+
	        				SPLITTER;

	    		}catch(Exception ioEx){
	    			String errMsg="error at analyzing module. "+
	    					ioEx.getClass()+ ": " +ioEx.getMessage();
	    			System.err.println(errMsg);
	    			output="100"+SPLITTER+errMsg+
	    					SPLITTER+SPLITTER+
	    					SPLITTER+SPLITTER;
	    		}
        	}
        }
		out.println(output);
		
		out.flush();
		out.close();
	}
	
	private static String fixSpecialChars(String input){
		String output="";
		if (input == null || input.equals(""))
			return output;
		/** kill special characters */
		output=StringEscapeUtils.unescapeXml(input);
		output=output.replace("'", PRIME);
		output=output.replace("\"", DOUBLE_PRIME);
		/** verify the html format & make the correct html form*/
		output=Jsoup.parse(output).text();
		/** remove all html tags and characters */
		String regEx="[&|<][/|\\w]+[;|>]";
		output.replaceAll(regEx, "");
		return output;
	}
	
	private static String resolveSpecialChars(String input){
		String output="";
		if (input == null || input.equals(""))
			return output;
		output=input.replace(PRIME, "'");
		output=output.replace(DOUBLE_PRIME, "\"");
		return output;
	}
	
	/**
	 * to analyze a webpage and get basic information including
	 * title, an image and description
	 * 
	 * @param url
	 * @return 3 strings describe basic information
	 * @throws ParserException
	 */
	private static String[] getPageInfo(String url)
								throws ParserException {
		String[] pInf = new String[3];
		Parser p = new Parser();
		
		HasAttributeFilter filter =null;
		NodeList nlist = null;

		p.setResource(url);
		int check=0;
		String type,data;
		
		/** 
		 * check if page is microdata style 
		 **/
		filter = new HasAttributeFilter("itemprop");
		nlist= p.parse(filter);

		for (Node n : nlist.toNodeArray()){
			type=((TagNode)n).getAttribute("itemprop").toLowerCase();
			data=((TagNode)n).getAttribute("content");
			if (data==null || data.equals("")){
				data=n.getText();
			}
			
			if (data!=null && !data.equals("")){
				if(type.contains("name")){
					pInf[0]=data;
					check++;
				}else if (type.contains("image")){
					if (isImageValid(data)){
						pInf[1]=data;
						check++;
					}
				}else if (type.contains("description")){
					pInf[2]=data;
					check++;
				}
			}
		}
		
		if (check==3)
			return pInf;
		
		/**
		 * check if page is open graph style
		 */
		p.reset();
		filter=new HasAttributeFilter("property");
		nlist=p.parse(filter);
		
		for (Node n: nlist.toNodeArray()){
			if (!(n instanceof MetaTag))
				continue;
			
			type=((MetaTag)n).getAttribute("property").toLowerCase();
			data=((MetaTag)n).getAttribute("content");
			if (data==null || data.equals("")){
				data=n.getText();
			}
			
			if (data!=null && !data.equals("")){
				if(type.contains("title") &&
						(pInf[0]==null || pInf[0].equals(""))){
					pInf[0]=data;
					check++;
				}else if (type.contains("image") &&
						(pInf[1]==null || pInf[1].equals(""))){
					if (isImageValid(data)){
						pInf[1]=data;
						check++;
					}
				}else if (type.contains("description") &&
						(pInf[2]==null || pInf[2].equals(""))){
					pInf[2]=data;
					check++;
				}
			}
		}
		
		if (check==3)
			return pInf;
		
		/**
		 * check if the page is meta description
		 */
		p.reset();
		filter=new HasAttributeFilter("name");
		nlist=p.parse(filter);
		
		for (Node n: nlist.toNodeArray()){
			if (!(n instanceof MetaTag))
				continue;
			
			type=((MetaTag)n).getAttribute("name").toLowerCase();
			data=((MetaTag)n).getAttribute("content");
			if (data==null || data.equals("")){
				data=n.getText();
			}
			
			if (data!=null && !data.equals("")){
				if(type.contains("title") &&
						(pInf[0]==null || pInf[0].equals(""))){
					pInf[0]=data;
					check++;
				}else if (type.contains("image") &&
						(pInf[1]==null || pInf[1].equals(""))){
					if (isImageValid(data)){
						pInf[1]=data;
						check++;
					}
				}else if (type.contains("description") &&
						(pInf[2]==null || pInf[2].equals(""))){
					pInf[2]=data;
					check++;
				}
			}
		}
		
		if (check==3)
			return pInf;
			
		/**
		 * if title is still unavailable
		 */
		if (pInf[0]==null || pInf[0].equals("")){
			TagNameFilter f= new TagNameFilter("title");
			p.reset();
			nlist=p.parse(f);
			
			for (Node n: nlist.toNodeArray()){
				if (!(n instanceof TitleTag))
					continue;
				pInf[0]=((TitleTag)n).getStringText();
				if (pInf[0]!=null && !pInf[0].equals("")){
					check++;
					break;
				}
			}
		}
		
		/**
		 * if title image is still unavailable 
		 */
		if (pInf[1]==null || pInf[1].equals("")){
			TagNameFilter f=new TagNameFilter("img");
			p.reset();
			nlist=p.parse(f);
			String imgUrl /*, alt*/;
			for (Node n: nlist.toNodeArray()){
				if (!(n instanceof ImageTag))
					continue;
				
				imgUrl=((ImageTag)n).getImageURL();
				if (imgUrl!=null)
					imgUrl=imgUrl.toLowerCase();
				
				if ((imgUrl.contains("jpg")||
						imgUrl.contains("png")||
						imgUrl.contains("bmp"))){
					if (isImageValid(imgUrl)){
						pInf[1]=imgUrl;
						break;
					}
				}

				/*
				alt=((ImageTag)n).getAttribute("alt");
				
				if ((imgUrl.contains("jpg")||
						imgUrl.contains("png")||
						imgUrl.contains("bmp")) &&
					(alt!=null && !alt.equals(""))){
					
					pInf[1]=imgUrl;
					break;
				}
				*/
			}
		}
		
		return pInf;
	}
	
	private static String createMainImage(String[] info, 
			String imgName, String savedPath) throws Exception {
		String newImagePath=savedPath+"/"+imgName;
		String ext=imgName.substring(imgName.lastIndexOf(".")+1);
		/*
		BufferedImage buff=new BufferedImage(STD_IMAGE_WIDTH, 
				STD_IMAGE_HEIGHT, BufferedImage.TYPE_INT_RGB);
		Graphics2D g =buff.createGraphics();
		
		BufferedImage img=ImageIO.read(new URL(info[1]));
		g.drawImage(img, 0, 0, 
				STD_IMAGE_WIDTH, STD_IMAGE_HEIGHT,null);

		AttributedString as = new AttributedString(info[0]);
		as.addAttribute(TextAttribute.FONT, 
					new Font("Tahoma", Font.BOLD, 20));
		as.addAttribute(TextAttribute.FOREGROUND, new Color(60, 150, 50));
		g.drawString(as.getIterator(), 20, 20);
		g.dispose();
		*/
		BufferedImage buff=createArticleImage(info);
		ImageIO.write(buff, ext, new File(newImagePath));
		
		return newImagePath;
	}
	
	/**
	 * this will return data in mode: <br>
	 * thumbs_filename<nsoft_splitter>
	 * org_image_width<nsoft_splitter>
	 * org_image_height
	 * 
	 * @param file
	 * @param savedPath
	 * @return
	 * @throws IOException
	 */
	private static String createThumb(File file, String savedPath) 
											throws IOException{
		String fullpath = file.getAbsolutePath();
		String ext = fullpath.substring(
				fullpath.lastIndexOf(".")).substring(1);
		
		if (EXT_ARRAY.contains(ext.substring(1).toLowerCase())){
			return createThumbImg(file, ext, savedPath);
		}
		return "";
	}
	
	private static String createThumbImg(File file, String ext, 
					String savedPath) throws IOException{
		String fileName = file.getName().substring(0, 
							file.getName().lastIndexOf("."));
		BufferedImage buff = ImageIO.read(file);
		String newFilename = fileName + "_thumb." + ext;
		String saveFile = savedPath + "/" + newFilename;
		BufferedImage img = new BufferedImage(
				100, 100, BufferedImage.TYPE_INT_RGB);
		img.createGraphics().drawImage(buff.getScaledInstance(
							100, 100, Image.SCALE_SMOOTH),0,0,null);
		ImageIO.write(img, ext, new File(saveFile));
		return saveFile+SPLITTER+
				buff.getWidth()+SPLITTER+
				buff.getHeight()+SPLITTER;
	}
	
	private static BufferedImage createArticleImage(
						String[] info) throws Exception {
		int RESIZE_IMG_WIDTH=600;
		int RESIZE_IMG_HEIGHT=450;
		BufferedImage buff=new BufferedImage(
				STD_IMAGE_WIDTH, STD_IMAGE_HEIGHT, 
				BufferedImage.TYPE_INT_ARGB);
		Graphics2D g=buff.createGraphics();
		
		g.setPaint(Color.WHITE);
	    g.fill(new java.awt.geom.Rectangle2D.Double(0, 0, 
	    		STD_IMAGE_WIDTH, STD_IMAGE_HEIGHT));
		
		String title=resolveSpecialChars(info[0]),
			imgUrl=info[1],
			description=resolveSpecialChars(info[2]);

		int endTitle = drawTextInBox(title, g, Font.BOLD, 22, 
				new Color(60, 150, 50), new Rectangle(10, 5, 290, 100));
		/*
		AttributedString as = new AttributedString(title);
		as.addAttribute(TextAttribute.FONT,
				new Font("LucidaSans", Font.BOLD, 22));
		as.addAttribute(TextAttribute.FOREGROUND, 
				new Color(60, 150, 50));
		g.drawString(as.getIterator(), 20, 20);
		*/
		
		drawTextInBox(description, g, Font.BOLD, 16, Color.BLACK, 
				new Rectangle(10, endTitle + 20, 290, 100));
		/*
		int wrappingWidth =245;
		
		AttributedString asDesc = new AttributedString(description);
		asDesc.addAttribute(TextAttribute.FONT, 
				new Font("LucidaSans", Font.BOLD, 16));
		asDesc.addAttribute(TextAttribute.FOREGROUND, Color.BLACK);
		
		FontRenderContext renderContext = g.getFontRenderContext();
		AttributedCharacterIterator chrIter = asDesc.getIterator(); 
		LineBreakMeasurer lbreaker = new LineBreakMeasurer(
				chrIter, renderContext);
		
		int x=20, y=70;
		while (lbreaker.getPosition() < chrIter.getEndIndex()){
			TextLayout textLayout = lbreaker.nextLayout(wrappingWidth);
			y += textLayout.getAscent();
			textLayout.draw(g, x, y);
			y += textLayout.getDescent() + textLayout.getLeading();
		}
		*/
		
		g.setRenderingHint(RenderingHints.KEY_INTERPOLATION, 
				RenderingHints.VALUE_INTERPOLATION_BILINEAR);
		BufferedImage orgImgBuff= ImageIO.read(new URL(imgUrl));
		g.drawImage(orgImgBuff, 310, 5, 
				RESIZE_IMG_WIDTH, RESIZE_IMG_HEIGHT, null);
		g.dispose();
		
		/*
		g.setComposite(AlphaComposite.Src);
		g.setRenderingHint(RenderingHints.KEY_INTERPOLATION,
				RenderingHints.VALUE_INTERPOLATION_BILINEAR);
		g.setRenderingHint(RenderingHints.KEY_RENDERING,
				RenderingHints.VALUE_RENDER_QUALITY);
		g.setRenderingHint(RenderingHints.KEY_ANTIALIASING,
				RenderingHints.VALUE_ANTIALIAS_ON);
		*/
		
		return buff;
	}
	
	private static int drawTextInBox(String t, Graphics2D g, 
			int ftype, int fsize, Color fcolor, Rectangle r){
		AttributedString asDesc = new AttributedString(t);
		asDesc.addAttribute(TextAttribute.FONT, 
				new Font("LucidaSans", ftype, fsize));
		asDesc.addAttribute(TextAttribute.FOREGROUND, fcolor);
		
		FontRenderContext renderContext = g.getFontRenderContext();
		AttributedCharacterIterator chrIter = asDesc.getIterator(); 
		LineBreakMeasurer lbreaker = new LineBreakMeasurer(
				chrIter, renderContext);
		
		int x=r.x, y=r.y;
		while (lbreaker.getPosition() < chrIter.getEndIndex()){
			TextLayout textLayout = lbreaker.nextLayout(r.width);
			y += textLayout.getAscent();
			textLayout.draw(g, x, y);
			y += textLayout.getDescent() + textLayout.getLeading();
		}
		return y;
	}
	
	private static boolean isImageValid(String imgUrl){
		int MIN_WIDTH=150;
		int MIN_HEIGHT=100;
		try{
			BufferedImage img=ImageIO.read(new URL(imgUrl));
			if (img!=null && img.getWidth() > MIN_WIDTH && 
					img.getHeight() > MIN_HEIGHT)
				return true;
			return false;
		}catch(IOException ioEx){ 
			return false;
		}
	}
}
